use crypto::{
    digest::Digest,
    sha3::{Sha3, Sha3Mode},
};

use crate::{
    error::{Error, ErrorKind},
    utils::byte_utils,
};

/// 验证地址是否合法
/// address: 是一个base32编码的字符串
pub fn is_valid_chash(address: &str) -> Result<(), Error> {
    let chash = base32::decode(base32::Alphabet::Crockford, address).ok_or(Error::from_kind(
        ErrorKind::Msg("address is not encoded by base32".to_string()),
    ))?;
    let mut binary_str = String::new();
    for b in chash {
        binary_str.push_str(&format!("{:08b}", b));
    }
    verify_eoa_address(&binary_str)?;
    verify_contract_address(&binary_str)?;
    Ok(())
}

/// panic: chash_length 不足以生成32个整数的数组，发生异常。
pub fn calc_offsets(chash_length: i32) -> [i32; 32] {
    let relative_offsets = PI.split("").filter(|x| x.len() > 0).collect::<Vec<_>>();
    let mut index = 0;
    let mut offset = 0;
    let mut integer_list = [0i32; 32];

    for i in 0..chash_length {
        let ro = relative_offsets[i as usize].parse::<i32>().unwrap();
        if ro == 0 {
            continue;
        }
        offset += ro;
        if chash_length == 288 {
            offset += 4;
        }
        if offset >= chash_length {
            break;
        }
        integer_list[index] = offset;
        index += 1;
    }
    if index != 32 {
        panic!("wrong number of checksum bits");
    }
    integer_list
}

/// 通过校验数据获取地址校验码
pub fn get_checksum(data: &[u8]) -> [u8; 4] {
    let mut hash = Sha3::new(Sha3Mode::Sha3_256);
    let mut hvalue = [0u8; 32];
    hash.input(data);
    hash.result(&mut hvalue);
    let mut ret = [0u8; 4];
    for i in 0..4 {
        ret[i] = hvalue[5 + i * 8];
    }
    ret
}

/// 融合校验码和校验数据在一起
/// panic: binary_checksum的长度不是32，发生异常，且binary_data的长度 + binary_checksum的长度之和不是160
///   发生异常。
pub fn mix_checksum_into_clean_data(binary_data: &str, binary_checksum: &str) -> String {
    let bin_checksum_len = binary_checksum.len();
    assert!(bin_checksum_len == 32);
    let len = binary_data.len() + bin_checksum_len;
    assert!(len == 160);
    let arr_offsets = calc_offsets(len as i32);

    let arr_checksum_bits = binary_checksum
        .split("")
        .filter(|x| x.len() > 0)
        .collect::<Vec<_>>();
    let mut start = 0usize;
    let mut arr_frags = vec![];

    for i in 0..arr_offsets.len() {
        let end = arr_offsets[i] as usize - i;
        arr_frags.push(&binary_data[start..end]);
        arr_frags.push(arr_checksum_bits[i]);
        start = end;
    }
    if start < binary_data.len() {
        arr_frags.push(&binary_data[start..]);
    }
    arr_frags.join("")
}

/// 验证EOA地址
/// binary_str: 二进制的字符串
fn verify_eoa_address(binary_str: &str) -> Result<(), Error> {
    let (checksum, data) = separate_into_clean_data_and_checksum(binary_str);
    let data = byte_utils::binary_str_to_bytes(&data)?;
    let checksum: Vec<u8> = byte_utils::binary_str_to_bytes(&checksum)?;
    if checksum != get_checksum(&data) {
        return Err(ErrorKind::Msg("verify failed".to_string()).into());
    }
    Ok(())
}

/// 验证合约地址
fn verify_contract_address(binary_str: &str) -> Result<(), Error> {
    let _ = byte_utils::binary_str_to_bytes(binary_str)?;
    if binary_str.len() == 160 {
        Ok(())
    } else {
        Err(ErrorKind::Msg("binary_str length is not 160, verify failed".to_string()).into())
    }
}

// 拆分校验码
// 返回校验码和校验数据
// panic: binary_str的长度不是160， 发生异常。
fn separate_into_clean_data_and_checksum(binary_str: &str) -> (String, String) {
    let len_bit = binary_str.len();
    let arr_offsets = if len_bit == 160 {
        // 根据PI获取32个有一定规律的数字
        calc_offsets(160)
    } else {
        panic!("must 160");
    };
    let mut arr_frags = vec![];
    let mut arr_checksum_bits = vec![];

    let mut start = 0usize;
    for i in 0..arr_offsets.len() {
        arr_frags.push(&binary_str[start..arr_offsets[i] as usize]);
        arr_checksum_bits.push(&binary_str[arr_offsets[i] as usize..arr_offsets[i] as usize + 1]);
        start += arr_offsets[i] as usize;
    }
    if start < len_bit {
        arr_frags.push(&binary_str[start..]);
    }
    let bin_clean_data = arr_frags.join("");
    let bin_checksum = arr_checksum_bits.join("");
    (bin_checksum, bin_clean_data)
}

const PI: &'static str = "14159265358979323846264338327950288419716939937510";
